<template>
<div>
    <div class="container">
        <div class="handle-box" >
            <div id="extend-upload-chooseFile"
                 style="float: left;margin: 0px 5px;">
                <i class="el-icon-plus"></i>
                选择文件
            </div>
            <el-button
                    type="success"
                    :icon="uploadStaus"
                    class="handle-del mr10"
                    @click="uploadToServer()"
                    :disabled="uploadBtnDisabled"
            >开始上传</el-button>
            <el-button
                    type="danger"
                    icon="el-icon-close"
                    class="handle-del mr10"
                    @click="clearFiles()"
                    :disabled="uploadBtnDisabled"
            >清空文件</el-button>

        </div>
        <div class="showMsg">支持上传的文件后缀：{{ options.fileType }}</div>
        <el-table
                :data="fileListData"
                style="width: 100%">
            <el-table-column
                    prop="fileName"
                    label="文件名称"
                    align="center"
                    width="180">

            </el-table-column>
            <el-table-column
                    prop="fileSize"
                    align="center"
                    label="文件大小"
                    width="180">
            </el-table-column>
            <el-table-column
                    prop="progress"
                    align="center"
                    label="进度">
            </el-table-column>
            <el-table-column label="操作" width="200" align="center"  fixed="right">
                <template slot-scope="scope">
                    <el-button
                            type="text"
                            icon="el-icon-close"
                            class="red"
                            @click="removeRow(scope.$index, scope.row)"
                    >移除</el-button>
                    <el-button
                            type="text"
                            icon="el-icon-search"
                            class="red"
                            @click="seeFile(scope.$index, scope.row)"
                    >查看</el-button>
                </template>
            </el-table-column>
        </el-table>
    </div>
</div>
</template>
<script>
import $ from 'jquery'
import webUploader from 'webuploader'
export default {
    name: 'webupload',
    props:{
        headers:{//自定义HTTP头信息，默认为空字符串
            type: String,
            default:''
        },
        fileNumLimit:{//允许同时上传的最大文件数量，默认为500个
            type:Number,
            default:500
        },
        fileSize:{//单个文件允许的最大大小，单位为字节，默认为10GB
            type:Number,
            default:100*1024*1024*1024
        },
        chunkSize:{//文件分片上传时，每片的大小，默认为5MB
            type: Number,
            default: 5*1024*1024
        },
        uploadSuffixUrl:{//上传服务的基础URl
            type:String,
            default: 'http://localhost:8081/'
        },
        options:{
            type:Object,
            default:{
                //允许上传的文件
                fileType:"doc,docx,pdf,xls,xlsx,ppt,pptx,gif,jpg,jpeg,bmp,png,rar,zip,mp4,avi",
                fileUploadUrl:"/upload/fileRecord/zone/upload",//上传地址
                fileCheckUrl:"/upload/fileRecord/zone/upload/md5Check",//检测文件是否存在url
                checkChunkUrl:"/upload/fileRecord/zone/upload/md5Check",//检测分片url
                mergeChunksUrl:"/upload/fileRecord/zone/upload/merge/",//合并文件请求地址
                headers:{}
            }
        },
        fileListData:{//用于存储已上传或待上传文件状态的数组
            type:Array,
            default:[]
        }

    },
    data() {
        return {
            uploader:{},
            uploadBtnDisabled:false,
            uploadStaus:"el-icon-upload",
            fList:[{
                numbers:1,
                fileName:"",
                fileSize:"120",
                progress:"20%",
                oper:""
            }]//文件集合
        }
    },
    mounted() {
        this.register();
        this.createUploader();
        this.events();
    },
    methods: {
        createUploader(){
            var fileType=this.options.fileType;
            this.uploader = webUploader.create({
                // 不压缩image
                resize: false,
                // swf文件路径
                swf: '../../../assets/Uploader.swf', // swf文件路径
                // 默认文件接收服务端。
                server: this.uploadSuffixUrl+this.options.fileUploadUrl,
                pick: {//当用户点击这个容器时，会弹出文件选择对话框，允许用户选择本地计算机上的文件准备上传
                    id: '#extend-upload-chooseFile',//指定选择文件的按钮容器，不指定则不创建按钮。注意 这里虽然写的是 id, 不仅支持 id, 还支持 class, 或者 dom 节点。
                    multiple :true //开启文件多选
                },
                // fileSingleSizeLimit:fileSize,//单个文件大小
                //定义允许用户选择和上传的文件类型
                accept:[{
                    title: 'file',
                    extensions: fileType,//用于指定允许上传的文件扩展名列表
                    mimeTypes: this.buildFileType(fileType)
                    //用于指定允许上传的文件MIME类型列表。buildFileType(fileType) 是一个方法，它根据 fileType 中的扩展名字符串构建对应的MIME类型列表。MIME类型是一种标准，
                    // 用于在互联网上标识文件的类型和编码方式，如 "image/jpeg"、"application/pdf" 等。这样，WebUploader不仅依据文件扩展名，还依据MIME类型来严格过滤用户可选择的文件。
                }],
                // 单位字节，如果图片大小小于此值，不会采用压缩。512k  512*1024，如果设置为0，原图尺寸大于设置的尺寸就会压缩；如果大于0，只有在原图尺寸大于设置的尺寸，并且图片大小大于此值，才会压缩
                compressSize: 0,
                fileNumLimit : this.fileNumLimit,//验证文件总数量, 超出则不允许加入队列,默认值：undefined,如果不配置，则不限制数量
                fileSizeLimit : 100*1024*1024*1024, //1kb=1024*1024,验证文件总大小是否超出限制, 超出则不允许加入队列。
                fileSingleSizeLimit:this.fileSize, //验证单个文件大小是否超出限制, 超出则不允许加入队列。
                chunked:true,//是否开启分片上传
                threads:1,
                chunkSize:this.chunkSize,//如果要分片，每一片的文件大小
                prepareNextFile:false//在上传当前文件时，准备好下一个文件,请设置成false，不然开启文件多选你浏览器会卡死
            });
        },
        //注册函数
        register(){
            var uploadSuffixUrl=this.uploadSuffixUrl;
            var  that=this;
            var options = this.options;
            var headers=options.headers||{};
            var fileCheckUrl=uploadSuffixUrl+options.fileCheckUrl;//检测文件是否存在url
            var checkChunkUrl=uploadSuffixUrl+options.checkChunkUrl;//检测分片url
            var mergeChunksUrl=uploadSuffixUrl+options.mergeChunksUrl;//合并文件请求地址

            //监控文件上传的三个时间点(注意：该段代码必须放在WebUploader.create之前)
            //时间点1：:所有分块进行上传之前（1.可以计算文件的唯一标记;2.可以判断是否秒传）
            //时间点2： 如果分块上传，每个分块上传之前（1.询问后台该分块是否已经保存成功，用于断点续传）
            //时间点3：所有分块上传成功之后（1.通知后台进行分块文件的合并工作）
            webUploader.Uploader.register({
                "before-send-file":"beforeSendFile",
                "before-send":"beforeSend",
                "after-send-file":"afterSendFile"
            },{
                //时间点1：:所有分块进行上传之前调用此函数
                beforeSendFile:function(file){
                    //利用md5File（）方法计算文件的唯一标记符 md5File() 方法是WebUploader库提供的
                    //md5File() 用于计算给定文件的MD5哈希值，这是一个广泛应用于数据校验和文件唯一性识别的算法。
                    // 在WebUploader中，通过调用此方法，可以在上传文件之前先计算出文件的MD5值，然后与服务器上已有的MD5值进行比较，以判断文件是否已经存在（即“秒传”），
                    // 或者在分片上传时用于验证分片数据的完整性（即“断点续传”）。这些功能大大提高了文件上传的效率和可靠性
                    //创建一个deffered 。Deferred对象是WebUploader库实现异步操作管理的一种工具，类似于Promise对象，用于处理异步操作的成功、失败和进度等状态。
                    // 在这里，创建Deferred对象是为了跟踪当前计算分块MD5值的操作进度，并在计算完成后触发相应的操作。
                    var deferred = webUploader.Deferred();
                    //1.计算文件的唯一标记，用于断点续传和秒传,获取文件前20m的md5值，越小越快，防止碰撞，把文件名文件大小和md5拼接作为文件唯一标识
                    //MD5值对文件内容的微小变化非常敏感
                    (new webUploader.Uploader()).md5File(file,0,10*1024*1024).progress(function(percentage){//计算整个文件的前20MB部分MD5值，用于秒传检测
                    }).then(function(val){
                        file.fileMd5=val; //val: 表示使用WebUploader库提供的md5File()方法或其他类似手段计算出的当前文件的MD5值
                        //file: 在WebUploader组件中，通常是一个表示上传文件的对象，它包含了文件的各种属性和方法，如文件名、大小、类型等。在此上下文中，file是待上传文件的具体实例
                        //2.请求后台是否保存过该文件，如果存在，则跳过该文件，实现秒传功能
                        $.ajax({
                            type:"POST",
                            url:fileCheckUrl,
                            headers:headers,
                            data:{
                                checkType:1,//这是一个自定义的参数，用来标识当前请求的类型或目的。数值1可能是前端和后端约定的一种标识，表示正在进行文件的MD5值检查
                                contentType:file.type,//提供文件的MIME类型（Multipurpose Internet Mail Extensions），
                                // 如image/jpeg、application/pdf等，服务器端可能需要此信息来确定如何处理上传的文件
                                zoneTotalMd5:val //存储文件的MD5值（val），这是请求的主要目的，服务器会使用这个值来判断文件是否已存在（即“秒传”），
                                // 或者在分片上传时用于验证分片数据的完整性，用于向后端发送文件的唯一标识符
                            },
                            dataType:"json",
                            success:function(response){
                                if(response.success){
                                    // 服务器确认文件已存在，可以秒传
                                    that.uploader.skipFile(file);// 跳过当前文件的实际上传过程
                                    that.setTableBtn(file.id,"文件秒传",response.data.networkPath);// 更新UI，显示文件秒传状态和网络路径
                                    that.$notify.success({
                                        showClose: true,
                                        message: `[ ${file.name} ]文件秒传`
                                    });
                                    //如果存在，则跳过该文件，秒传成功将秒传成功的文件数据添加到组件内部数据列表
                                    that.fList.push(response.data);
                                    deferred.reject();// 告诉上传队列，此文件已完成（通过秒传）
                                }else{
                                    if(response.code === 30001){// 服务器返回错误代码，表示不支持此文件类型
                                        var m=response.message+"，文件后缀："+file.ext;
                                        that.uploader.skipFile(file);// 跳过当前文件的实际上传过程
                                        that.setTableBtn(file.id,m); // 更新UI，显示错误信息
                                        //如果存在，则跳过该文件，秒传成功 // 将文件数据（含错误信息）添加到组件内部数据列表
                                        that.fList.push(response.data);
                                        deferred.reject();// 告诉上传队列，此文件因不支持类型而未能上传
                                    }else{
                                        //继续上传
                                        deferred.resolve(); // 其他非秒传、非类型错误的情况，继续进行文件上传流程
                                    }
                                }
                            }
                        });

                    });
                    //返回deffered
                    return deferred.promise();
                },
                //时间点2：如果有分块上传，则 每个分块上传之前调用此函数
                //block:代表当前分块对象
                beforeSend:function(block){
                    //block代表了当前正在处理的文件分块的信息
                    //向后台发送当前文件的唯一标记，用于后台创建保存分块文件的目录
                    //1.请求后台是否保存过当前分块，如果存在，则跳过该分块文件，实现断点续传功能
                    var deferred = webUploader.Deferred();
                    //请求后台是否保存完成该文件信息，如果保存过，则跳过，如果没有，则发送该分块内容
                    (new webUploader.Uploader()).md5File(block.file,block.start,block.end).progress(function(percentage){//计算文件分块的MD5值，用于断点续传校验。block.start和block.end的值通常是在WebUploader进行文件分块处理时自动计算并赋予给block对象的属性
                    }).then(function(val){
                        block.zoneMd5=val;
                        $.ajax({
                                type:"POST",
                                url:checkChunkUrl,
                                headers:headers,
                                data:{
                                    checkType:2,
                                    zoneTotalMd5:block.file.fileMd5,//block.file属性则指向了与当前分块所属的完整文件对象 在beforeSendFile中声明了file.fileMd5=val 所以这里可以通过block.file.fileMd5拿到
                                    zoneMd5:block.zoneMd5
                                },
                                dataType:"json",
                                success:function(response){
                                    if(response.success){
                                        //分块存在，跳过该分块
                                        deferred.reject();
                                    }else{
                                        //分块不存在或者不完整，重新发送该分块内容
                                        deferred.resolve();
                                    }
                                }
                            }
                        );
                    });
                    return deferred.promise();
                },
                //时间点3：所有分块上传成功之后调用此函数
                afterSendFile:function(file){
                    //前台通知后台合并文件
                    //1.如果分块上传，则通过后台合并所有分块文件
                    //请求后台合并文件
                    $.ajax({
                        type:"POST",
                        url:mergeChunksUrl+file.fileMd5,//传file.fileMd5是告诉服务器合并那个文件
                        headers:headers,
                        dataType:"JSON",
                        success:function(resdata){
                            if(resdata.success){//存在了，+1
                                var data=resdata.data.fileInfo;
                                that.uploader.skipFile(file);//// 跳过当前文件的实际上传过程
                                that.setTableBtn(file.id,'上传成功',data.networkPath);
                                that.fList.push(data);
                            }
                        }
                    });
                }
            });
        },
        //主要是注册和监听 WebUploader 插件的各种事件
        events(){
            var that = this;
            var uplaod=this.uploader;
            //当文件添加进去
            uplaod.on('fileQueued', function( file ){
                var fileSize = that.formatFileSize(file.size);
                var row={fileId:file.id,fileName:file.name,fileSize:fileSize,validateMd5:'0%',progress:"等待上传",state:'就绪'};
                console.log("this.fileListData:",that.fileListData);
                that.fileListData.push(row);
            });

            //监听进度条,更新进度条信息
            //在文件上传过程中，每当文件的一部分数据完成传输时触发。这里实时更新文件的上传进度百分比，并通过 setTableBtn 方法将进度反馈到前端UI上。
            uplaod.on( 'uploadProgress', function( file, percentage ) {
                //percentage值完全由WebUploader库内部计算和提供
                that.setTableBtn(file.id,(percentage * 100).toFixed(2)+'%');
            });

            /**上传之前**/
            //在文件上传前发送HTTP请求之前触发，用于设置额外的数据和HTTP头部信息，以确保上传时携带正确的参数，如MD5值、分片信息等
            uplaod.on('uploadBeforeSend', function( block, data, headers ) {
                // block 对象代表当前上传任务的一个分块信息
                // data对象是要附加到HTTP POST请求体中的数据
                // headers 对象是即将发送的HTTP请求头部信息
                // .file: 整个文件对象，可以从该对象获取整个文件的MD5值 (fileMd5) 和文件类型 (type)
                // .file.fileMd5: 整个文件的MD5哈希值，用于文件完整性校验或秒传。
                // .file.type: 文件MIME类型，表示文件的内容类型。
                // .chunks: 如果文件被切分为多个分块，这个属性代表文件总共有多少个分块。
                // .zoneMd5: 当前分块的MD5值，用于断点续传时校验分块的完整性。
                // .chunk: 当前处理的分块编号，从0开始计数。
                // .total: 文件总大小。
                // .start: 当前分块的起始字节位置。
                // .end: 当前分块的结束字节位置。

                // fileMd5 表示整个文件的MD5值，它是通过对整个文件内容进行MD5算法计算得出的一个固定长度的哈希值。在文件上传的场景中，
                // 这个值通常用于判断文件是否已经存在于服务器上，即“秒传”功能。如果服务器上已经有了同样MD5值的文件，说明两个文件内容一致，可以直接跳过上传过程。

                // zoneMd5 则通常指某个文件分片的MD5值。在文件分片上传的过程中，当文件被切割成多个较小的块（分片）逐个上传时，每个分片都会有一个独立的MD5值。
                // 这个值用来在断点续传时检测服务器上对应分片是否已经完整上传，如果已经上传，则无需再次上传该分片，否则将继续上传该分片数据

                data.fileMd5 = block.file.fileMd5;
                data.contentType=block.file.type;
                data.chunks = block.file.chunks;
                data.zoneTotalMd5= block.file.fileMd5;
                data.zoneMd5=block.zoneMd5;
                data.zoneTotalCount=block.chunks;
                data.zoneNowIndex=block.chunk;//当前文件索引
                data.zoneTotalSize=block.total;//文件总大小
                data.zoneStartSize=block.start;
                data.zoneEndSize=block.end;
                headers.Authorization=that.options.headers.Authorization;
            });
            //  // 处理不同类型的错误，如文件过大、类型不允许等，并通过消息提示用户。
            //错误信息监听
            uplaod.on('error', function(handler){
                // F_EXCEED_SIZE 和 Q_TYPE_DENIED 是WebUploader库中预定义的错误码，它们在文件上传过程中出现错误时触发：
                if(handler=='F_EXCEED_SIZE'){
                    //F_EXCEED_SIZE这个错误码意味着上传的单个文件大小超过了所设置的最大限制
                    that.$message.error({
                        showClose: true,
                        message: '上传的单个太大!\n最大支持'+that.formatFileSize(that.fileSize)+'! \n操作无法进行,如有需求请联系管理员'
                    });
                }else if(handler=='Q_TYPE_DENIED'){
                    //Q_TYPE_DENIED这个错误码表示用户试图上传的文件类型不符合预定义的允许上传文件类型列表
                    that.$message.error({
                        showClose: true,
                        message: '不允许上传此类文件!。<br>操作无法进行,如有需求请联系管理员'
                    });
                }
            });

            /**从文件队列移除**/
            //当文件从上传队列中移除时触发，此处未完全展示逻辑，但通常会更新UI，移除文件的相关状态信息。
            uplaod.on('fileDequeued', function( file ) {
                // delete percentages[ file.id ];
            });

            //当文件上传成功时触发。file {ArchivesFile} File对象, response {Object}服务端返回的数据
            uplaod.on('uploadSuccess',function(file,response){
                // debugger;
                if(response.success){
                    that.setTableBtn(file.id,'正在校验文件...');
                }
            })
            //所有文件上传成功后
            uplaod.on('uploadFinished',function(){//成功后
                that.uploadBtnDisabled=false;
                that.uploadStaus="el-icon-upload";
                that.$message.success({
                    showClose: true,
                    message: '文件上传完毕'
                });

            });
        },
        //更新文件上传状态表单中的按钮状态或文本信息
        setTableBtn(fileId,showmsg,networkPath){
            //networkPath 表示文件上传到服务器后，该文件在网络服务器上的绝对路径或相对路径。这个路径通常是服务器返回给客户端的，
            // 用来标识上传成功后文件在网络存储系统中的位置，以便后续进行文件访问、下载或其他进一步操作
            var fileList=this.fileListData;
            for(var i=0;i<fileList.length;i++){
                if(fileList[i].fileId==fileId){
                    this.fileListData[i].progress=showmsg;
                    this.fileListData[i].networkPath=networkPath||"";
                }
            }
        },
        //查看
        removeRow(index,row){
            console.log("index:",index);
            console.log("row:",row);
            //这一行代码在 removeRow 函数中执行了一个关键操作：从WebUploader实例中移除一个已添加到上传队列的文件。
            this.uploader.removeFile(row.fileId);
            this.fileListData.splice(index,1);
        },
        //删除
        seeFile(index,row){
            console.log("row:",row);
            var npath=row.networkPath;
            if(this.strIsNull(npath)){
                this.$message.error({
                    showClose: true,
                    message: '文件未上传，请等待文件上传完成后，方可查看'
                });
                return;
            }
            var filens=npath.substring(npath.lastIndexOf(".")+1);
            if(filens=="png"||filens=="jpg"||filens=="jpeg"
                ||filens=="gif"){//图片，在当前窗口查看
                this.$message.success({
                    dangerouslyUseHTMLString: true,
                    showClose: true,
                    message: `<img src="${this.uploadSuffixUrl+npath}" style="max-width: 80%;max-height: 80%;"/>`
                });
            }else{
               window.open(this.uploadSuffixUrl+npath);
            }
        },
        //开始上传
        uploadToServer(){
            if(this.fileListData.length<=0){
                this.$message.error({
                    showClose: true,
                    message: '没有上传的文件'
                });
                return;
            }
            this.uploadBtnDisabled=true;
            this.uploadStaus="el-icon-loading";
            $("#extent-button-uploader").text("正在上传，请稍等...");
            // $("#extent-button-uploader").addClass('layui-btn-disabled');
            this.uploader.upload();//是对WebUploader实例调用的 upload 方法，其作用是启动文件上传过程。
        },

        //清空文件
        clearFiles(){
            var that=this;
            that.uploadBtnDisabled=false;
            that.uploadStaus="el-icon-upload";
            that.uploader.reset();
            that.fileListData=[];
        },
        beginUploadFile(index,row){
            this.uploadBtnDisabled=false;
            this.uploadStaus="el-icon-upload";
            this.uploader.upload();
        },
        stopUploadFile(index,row){
            this.uploadBtnDisabled=false;
            this.uploadStaus="el-icon-upload";
            this.uploader.stop();
        },
        buildFileType(fileType){
            var ts = fileType.split(',');
            var ty='';

            for(var i=0;i<ts.length;i++){
                ty=ty+ "."+ts[i]+",";
            }
            return  ty.substring(0, ty.length - 1)
        },
        //判断是否为空
        strIsNull(str){
            if(typeof str == "undefined" || str == null || str == "")
                return true;
            else
                return false;
        },
        formatFileSize(size){
            var fileSize =0;
            if(size/1024>1024){
                var len = size/1024/1024;
                fileSize = len.toFixed(2) +"MB";
            }else if(size/1024/1024>1024){
                var len = size/1024/1024;
                fileSize = len.toFixed(2)+"GB";
            }else{
                var len = size/1024;
                fileSize = len.toFixed(2)+"KB";
            }
            return fileSize;
        },

    }
}
</script>
<style>
    .container {
        padding: 30px;
        background: #fff;
        border: 1px solid #ddd;
        border-radius: 5px;
    }
    .handle-box {
        margin-bottom: 20px;
    }

    #picker div:nth-child(2){width:100%!important;height:100%!important;}
    .webuploader-container {
        position: relative;
    }
    .webuploader-element-invisible {
        position: absolute !important;
        clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
        clip: rect(1px,1px,1px,1px);
    }
    .webuploader-pick {
        position: relative;
        display: inline-block;
        cursor: pointer;
        background:#409eff;
        padding: 8px 12px;
        color: #fff;
        font-size: 10px;
        text-align: center;
        border-radius: 3px;
        overflow: hidden;
    }
    .webuploader-pick-hover {
        background: #409EFF;
    }

    .showMsg{
        margin: 5px;
        background: radial-gradient(#d2b8b8, transparent);
    }
</style>
